package za.um.td.entity.level;

import java.util.Properties;

import za.um.td.entity.Placable;

import com.jme.entity.Entity;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;

public abstract class AbstractTerrain extends Entity {

	protected GridBlockType[][] gridBlocks;
	protected int halfNumBlocksX, halfNumBlocksZ;

	/**
	 * Grid block types
	 * 
	 * @author koekemoere
	 * 
	 */
	public enum GridBlockType {
		/** Can't move through or place on */
		SOLID
	}

	/**
	 * The default x and z sizes of the grid blocks
	 */
	public static final Vector2f GRID_BLOCK_SIZE = new Vector2f(1.0f, 1.0f);

	public AbstractTerrain(String id) {
		super(id);
	}

	public AbstractTerrain(String id, Properties properties) {
		super(id, properties);
	}

	/**
	 * Initialises this class' members. Should be called after all child class
	 * members have been initialised
	 */
	protected void initAbstractTerrainMembers() {
		gridBlocks = new GridBlockType[getNumBlocksX()][getNumBlocksZ()];
		halfNumBlocksX = getNumBlocksX() >> 1;
		halfNumBlocksZ = getNumBlocksZ() >> 1;
	}

	/**
	 * Sets the grid at the specified coordinates to the specified type. The
	 * grid's axes is in the centre! Thus, if you want to change coordinate
	 * (0;0) (parameters: x == 0 and z == 0) then array position
	 * ((getNumBlocksX() / 2) + x; (getNumBlocksZ() / 2) + z)) will get the
	 * value of gridBlockType.
	 * 
	 * @param x
	 *            The x coordinate of the grid to set
	 * @param z
	 *            The z coordinate of the grid to set
	 * @param gridBlockType
	 *            The block type to set the grid block to
	 */
	public void setGridBlock(int x, int z, GridBlockType gridBlockType) {
		gridBlocks[halfNumBlocksX + x][halfNumBlocksZ + z] = gridBlockType;
	}

	public GridBlockType getGridBlock(int x, int z) {
		return gridBlocks[halfNumBlocksX + x][halfNumBlocksZ + z];
	}

	/**
	 * Same as setGridBlock(int x, int z, GridBlockType gridBlockType), but it
	 * uses the terrain coordinates and then maps them to grid coordinates
	 * 
	 * @param terrainCoordinates
	 * @param gridBlockType
	 */
	public void setGridBlock(Vector3f terrainCoordinates,
			GridBlockType gridBlockType) {
		setGridBlock(Math.round(terrainCoordinates.x), Math
				.round(terrainCoordinates.z), gridBlockType);
	}

	/**
	 * Sets all the grid blocks occupied by the placable
	 * 
	 * @param placable
	 */
	public void setGridBlocks(Placable placable) {

		int placeBlockGridX = Math.round(placable.getSpatial()
				.getLocalTranslation().x);
		int placeBlockGridZ = Math.round(placable.getSpatial()
				.getLocalTranslation().z);

		for (Vector2f placeBlock : placable.getPlaceBlocks()) {
			setGridBlock(placeBlockGridX + (int) placeBlock.x, placeBlockGridZ
					+ (int) placeBlock.y, GridBlockType.SOLID);
		}
	}

	public boolean isPlacable(Placable placable) {

		int placeBlockGridX = Math.round(placable.getSpatial()
				.getWorldTranslation().x);
		int placeBlockGridZ = Math.round(placable.getSpatial()
				.getWorldTranslation().z);

		for (Vector2f placeBlock : placable.getPlaceBlocks()) {
			GridBlockType gridBlock = getGridBlock(placeBlockGridX
					+ (int) placeBlock.x, placeBlockGridZ + (int) placeBlock.y);
			if (gridBlock != null && gridBlock.equals(GridBlockType.SOLID)) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Returns the height of the terrain at the specified location
	 * 
	 * @param location
	 *            The location of the height to return
	 * @return the height of the terrain at the specified location
	 */
	public abstract float getHeight(Vector3f location);

	/**
	 * @return the total length of the terrain along the X axes
	 */
	public abstract float getXLength();

	/**
	 * @return the total length of the terrain along the Z axes
	 */
	public abstract float getZLength();

	/**
	 * @return the number of grid blocks along the X axes
	 */
	public abstract int getNumBlocksX();

	/**
	 * @return the number of grid blocks along the Z axes
	 */
	public abstract int getNumBlocksZ();

	/**
	 * @return the location of the very centre of the terrain
	 */
	public abstract Vector3f getCentreLocation();

	/**
	 * @return the amount the terrain is scaled by on each of its axis
	 */
	public abstract Vector3f getScale();

}
